(ns clojure-demo.macro
  (:use [clojure.java.io]))

;3，用宏来实现类似java5的改进型for。

;p233

(defmacro foreach [[sym coll] & body]
    `(loop  [coll# ~coll]
            (when-let [[~sym & xs#] (seq coll#)]    ;如果coll可以转成一个序列（当coll为nil时失败），则序列的第一个元素绑定到sym，其它元素绑定到xs# 。
                      ~@body
                     (recur xs#))))
;= #'user/foreach

(foreach [x [1 2 3]]
  (println x))
; 1
; 2
; 3

;;;;;;;;;;;;;
;4，写一个宏，把clojure里面所有的符号倒过来写。

;例如写(reverse-it (nltnirp "foo")) ，最终求值成 (println "foo")。

(require '(clojure  [string :as str]
                    [walk :as walk]))

(defmacro reverse-it
    [form] 
    (walk/postwalk #(if (symbol? %) 
                        (symbol (str/reverse (name %))) 
                        %)
                    form))

;postwalk函数递归地对form的每个元素执行给定的匿名函数。

;匿名函数把form里面的所有符号替换成相反的名字，而对于非符号的元素则不进行处理。

;--
(reverse-it
    (qesod [gra (egnar 5)]
           (nltnirp (cni gra))))
; 1
; 2
; 3
; 4
; 5
;= nil

;--
(macroexpand-1 '(reverse-it
                    (qesod [gra (egnar 5)]
                           (nltnirp (cni gra)))))
;= (doseq [arg (range 5)]
;= (println (inc arg)))

;==========
;5，调试宏（把宏展开）

;macroexpand-1 ：只扩展宏一次。

;macroexpand ：扩展宏，知道最顶级形式不再是一个宏。

(macroexpand '(cond a b c d))
;= (if a b (clojure.core/cond c d))

;----
;完全扩展宏

(require '[clojure.walk :as w])

(w/macroexpand-all '(cond a b c d))
;= (if a b (if c d nil))

;--
;只是模拟编译器，但对于特殊形式的处理不一定正确。

(w/macroexpand-all ''(when x a))
;= (quote (if x (do a)))

;事实上应该扩展成 (quote (when x a)) ，不应该扩展quote后面的代码。

;;;;;;;;;;;;;;;
;看一下语法引述的实现。

'`(map println ~[foo])
;= (clojure.core/seq
;=     (clojure.core/concat
;=         (clojure.core/list (quote clojure.core/map))
;=         (clojure.core/list (quote clojure.core/println))
;=         (clojure.core/list [foo])))

;;;;;;;;;;;;
(defn fn-hello [x]
      (str "Hello, " x "!"))

(defmacro   macro-hello [x]
            `(str "Hello, " ~x "!"))

(fn-hello "Brian")
;= "Hello, Brian!"

(macro-hello "Brian")
;= "Hello, Brian!"

(map fn-hello ["Brian" "Not Brian"])
;= ("Hello, Brian!" "Hello, Not Brian!")

(map macro-hello ["Brian" "Not Brian"])
;= #<CompilerException java.lang.RuntimeException:
;= Can't take value of a macro: #'user/macro-hello, compiling:(NO_SOURCE_PATH:1)>


;;;;;;;;;;;;
;11，自己实现串行宏。

;首先，验证一个形式是不是序列，如果不是，构造一个只包含这个形式的序列。

(defn ensure-seq [x]
    (if (seq? x) x (list x)))

(ensure-seq 'x)
;= (x)

(ensure-seq '(x))
;= (x)

;接着，对于给定的两个形式x和ys，把x插入到ys的第二个元素的位置上，并且确保ys是一个序列。

(defn insert-second
    "Insert x as the second item in seq y."
    [x ys]
    (let [ys (ensure-seq ys)]
         (concat (list (first ys) x)
                 (rest ys))))

(defn insert-second
    "Insert x as the second item in seq y."
    [x ys]
    (let [ys (ensure-seq ys)]
         `(~(first ys) ~x ~@(rest ys))))
;语法引述可以在函数中使用。

(defn insert-second
    "Insert x as the second item in seq y."
    [x ys]
    (let [ys (ensure-seq ys)]
         (list* (first ys) x (rest ys))))

(defmacro thread
    "Thread x through successive forms."
    ([x] x)
    ([x form] (insert-second x form))
    ([x form & more] `(thread (thread ~x ~form) ~@more)))
;宏可以支持不同个数的参数。


(thread [1 2 3] (conj 4) reverse println)
;= (4 3 2 1)

(-> [1 2 3] (conj 4) reverse println)
;= (4 3 2 1)

;--
;也可以用函数来实现“串行宏”。

(defn thread-fns
    ([x] x)
    ([x form] (form x))
    ([x form & more] (apply thread-fns (form x) more)))

(thread-fns [1 2 3] reverse #(conj % 4) prn)
;= (4 3 2 1)
;这里需要用匿名函数。
;而且这个函数版本不支持java的方法调用。

(thread [1 2 3] .toString (.split " ") seq)
;= ("[1" "2" "3]")

(thread-fns [1 2 3] .toString #(.split % " ") seq)
;= #<CompilerException java.lang.RuntimeException:
;= Unable to resolve symbol: .toString in this context, 
compiling:(NO_SOURCE_PATH:1)>
;; This is starting to look a bit hairy...

(thread-fns [1 2 3] #(.toString %) #(.split % " ") seq)
;= ("[1" "2" "3]")
